package com.mswyt.utils.msValidation;

import com.mswyt.utils.base.MsAopUtils;
import com.mswyt.utils.base.MsAssertUtils;
import com.mswyt.utils.base.MsEmptyUtils;
import com.mswyt.utils.msValidation.annotation.MsCheckMapParams;
import com.mswyt.utils.msValidation.annotation.MsCheckParams;
import org.apache.commons.collections.MapUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

@Aspect
@Component
public class MsCheckParamAop {

    private static final String NO_USER_BATCH = "不可以同时使用@MsCheckMapParams，@MsCheckParams注解";

    /**
     * 声明一个切入点，范围为controller包下所有的类
     * 注：作为切入点签名的方法必须返回void类型
     */
    @Pointcut("@annotation(com.mswyt.utils.msValidation.annotation.MsCheckMapParams) || @annotation(com.mswyt.utils.msValidation.annotation.MsCheckParams) ")
    private void pointcut() {
    }

    /**
     * 前置通知：在某连接点之前执行的通知，但这个通知不能阻止连接点之前的执行流程（除非它抛出一个异常）
     */

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        //获取目标方法的参数信息
        Object[] args = joinPoint.getArgs();
        //判断是否需要检查入参
        MsCheckMapParams mapA = MsAopUtils.getAnnotation(joinPoint, MsCheckMapParams.class);
        MsCheckParams objA = MsAopUtils.getAnnotation(joinPoint, MsCheckParams.class);
        if (objA != null) {
            checkObjParams(args, objA);
        }
        //优先检验MsCheckParams注解
        if (mapA != null) {
            checkMapParams(args, mapA);
        }
    }

    private void checkObjParams(Object[] args, MsCheckParams objA) {
        int[] excludeIndex = objA.excludeIndex();
        Set<Integer> excludeSet = Arrays.stream(excludeIndex).boxed().collect(Collectors.toSet());
        for (int i = 0; i < args.length; i++) {
            if (!excludeSet.contains(i)) {
                if (args.length == 1) {
                    MsAssertUtils.objIsNull(args[i], MsAssertUtils.MSG);
                } else {
                    MsAssertUtils.objIsNull(args[i], "方法中第【" + (i + 1) + "】参数值为空");
                }
            }
        }
    }


    private void checkMapParams(Object[] args, MsCheckMapParams mapA) {
        for (Object arg : args) {
            if (arg instanceof Map) {
                List<String> msgList = new CopyOnWriteArrayList<>();
                Map<String, Object> map = (Map<String, Object>) arg;
                String[] keys = mapA.keys();
                String message = mapA.message();
                //如果为0，则所有参数都检验
                if (keys.length == 0) {
                    keys = map.keySet().toArray(new String[0]);
                }
                for (String key : keys) {
                    Object val = MapUtils.getObject(map, key);
                    if (val == null || MsEmptyUtils.isEmpty(String.valueOf(val))) {
                        msgList.add("必填参数【" + key + "】值为空");
                    }
                }
                //优先抛出自定义异常
                if (msgList.size() != 0) {
                    MsAssertUtils.throwException(MsEmptyUtils.isNotEmpty(message) ? message : msgList.stream().collect(Collectors.joining(" ; ")));
                }
                return;
            }
        }
    }
//
//    @AfterReturning(value = "pointcut()", returning = "keys")
//    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
//        System.err.println("第一个后置返回通知的返回：" + keys);
//    }
//
//    /**
//     * 后置通知：在某连接点正常完成后执行的通知，通常在一个匹配的方法返回的时候执行
//     *
//     * @param joinPoint
//     * @return
//     */
//    @After(value = "pointcut()")
//    public void doAfter(JoinPoint joinPoint) {
//        System.err.println("--最后通知---");
//    }
//
//    @Around(value = "pointcut()")
//    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        System.err.println("环绕通知...");
//        return  joinPoint.proceed();
//    }
}